home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 398 / 398.xpi / chrome / forecastfox.jar / content / icons / pack-service.js < prev    next >
Text File  |  2010-02-04  |  36KB  |  1,242 lines

  1. /*------------------------------------------------------------------------------
  2.   Copyright (c) 2008 Ensolis, LLC. All Rights Reserved.
  3.   ----------------------------------------------------------------------------*/
  4.  
  5. /******************************************************************************
  6.  * DTD and Namespace Constants for icons.xml
  7.  *****************************************************************************/
  8. const ICONS_DTD = "http://forecastfox.ensolis.com/specs/1.0/icons.dtd";
  9. const ICONS_NS = "http://forecastfox.ensolis.com/specs/1.0/icons";
  10.  
  11. /******************************************************************************
  12.  * Icon Pack Constants
  13.  *****************************************************************************/
  14. const PACK_ROOT = "http://www.ensolis.com/2005/icon-pack/install"; 
  15. const PACK_PREFIX = "http://www.ensolis.com/2005/icon-manager#";
  16. const PACK_PROPERTIES = ["id", "name", "version", "website", "author", 
  17.                          "target", "smallWidth", "smallHeight", "largeWidth", 
  18.                          "largeHeight"];  
  19. const PACK_FILES = ["pack.rdf", "small/01.png", "small/02.png", "small/03.png", 
  20.                     "small/04.png", "small/05.png", "small/06.png", 
  21.                     "small/07.png", "small/08.png", "small/11.png", 
  22.                     "small/12.png", "small/13.png", "small/14.png", 
  23.                     "small/15.png", "small/16.png", "small/17.png", 
  24.                     "small/18.png", "small/19.png", "small/20.png", 
  25.                     "small/21.png", "small/22.png", "small/23.png", 
  26.                     "small/24.png", "small/25.png", "small/26.png", 
  27.                     "small/29.png", "small/30.png", "small/31.png", 
  28.                     "small/32.png", "small/33.png", "small/34.png", 
  29.                     "small/35.png", "small/36.png", "small/37.png", 
  30.                     "small/38.png", "small/39.png", "small/40.png", 
  31.                     "small/41.png", "small/42.png", "small/43.png", 
  32.                     "small/44.png", "small/radar.png", "small/swa.png", 
  33.                     "large/01.png", "large/02.png", "large/03.png", 
  34.                     "large/04.png", "large/05.png", "large/06.png", 
  35.                     "large/07.png", "large/08.png", "large/11.png", 
  36.                     "large/12.png", "large/13.png", "large/14.png", 
  37.                     "large/15.png", "large/16.png", "large/17.png", 
  38.                     "large/18.png", "large/19.png", "large/20.png", 
  39.                     "large/21.png", "large/22.png", "large/23.png", 
  40.                     "large/24.png", "large/25.png", "large/26.png", 
  41.                     "large/29.png", "large/30.png", "large/31.png", 
  42.                     "large/32.png", "large/33.png", "large/34.png", 
  43.                     "large/35.png", "large/36.png", "large/37.png", 
  44.                     "large/38.png", "large/39.png", "large/40.png", 
  45.                     "large/41.png", "large/42.png", "large/43.png", 
  46.                     "large/44.png", "large/swa.png"];
  47.                     
  48. /******************************************************************************
  49.  * Reads an icon pack file from disk validates the pack has the required
  50.  * files and properties and creates a icon pack item.  If the pack file is not
  51.  * valid then a error item is created.
  52.  * This implements the nsIRunnable interface so it could 
  53.  * be dispatched to a thread.
  54.  *
  55.  * @param   File to read.
  56.  * @return  An ffIPackItem if the file is valid, otherwise a ffIErrorItem is
  57.  *          returned.  Callers should check the instanceof to determine.
  58.  *****************************************************************************/
  59. function PackReader(aFile)
  60. {
  61.   this.file = aFile;
  62. }
  63. PackReader.prototype = {
  64.   _file: null,
  65.   _item: null,
  66.   _dskSvc: null,
  67.   _rdfSvc: null,
  68.   _temp: null,
  69.   _bundle: null,
  70.     
  71.   get file() { return this._file; },
  72.   set file(aVal) { this._file = aVal; },
  73.   
  74.   get item() { return this._item; },
  75.   set item(aVal) { this._item = aVal; },
  76.   
  77.   get bundle() { return this._bundle; },
  78.              
  79.   ///////////////////////////
  80.   // nsISupports
  81.   QueryInterface: function PackReader_QueryInterface(aIID)
  82.   {
  83.     if (!aIID.equals(Ci.nsIRunnable) &&      
  84.         !aIID.equals(Ci.nsISupports))
  85.       throw Components.results.NS_ERROR_NO_INTERFACE;
  86.     return this;
  87.   },
  88.              
  89.   ///////////////////////////
  90.   // nsIRunnable      
  91.   run: function PackReader_run()
  92.   {
  93.     //initialize the needed variables
  94.     this._init();
  95.     
  96.     //open the zip file
  97.     var reader = Cc["@mozilla.org/libjar/zip-reader;1"].
  98.                  createInstance(Ci.nsIZipReader);
  99.     try {
  100.       //toolkit 2.0 and prior or non toolkit
  101.       if ("init" in reader) {
  102.         reader.init(this.file);
  103.         reader.open();
  104.         
  105.       //toolkit 3.0
  106.       } else 
  107.         reader.open(this.file);    
  108.     
  109.     //error opening the zip file
  110.     } catch(e) {
  111.       this._setError("open", this.file.path);
  112.       this._destroy(reader);
  113.       return;
  114.     }
  115.     
  116.     //get the zip entries
  117.     var entries = this._getEntries(reader);
  118.     
  119.     //check the entries are valid
  120.     if (!this._checkEntries(entries)) {
  121.       this._destroy(reader);
  122.       return;
  123.     }
  124.     
  125.     //get the install manifest
  126.     var manifest = this._getManifest(reader);
  127.     
  128.     //check the manifest is valid
  129.     if (!this._checkManifest(manifest)) {
  130.       this._destroy(reader);
  131.       return;
  132.     }
  133.     
  134.     //create the pack item
  135.     this._item = this._createItem(manifest);
  136.     
  137.     //cleanup and return
  138.     this._destroy(reader);
  139.     return;    
  140.   },
  141.   
  142.   /**
  143.    * Initialize internal variables.
  144.    */
  145.   _init: function PackReader__init()
  146.   {
  147.     //get the stringbundle
  148.     this._bundle = getBundle();
  149.     
  150.     //get the disk service
  151.     var mgrSvc = Cc["@ensolis.com/forecastfox/manager-service;1"].
  152.                  getService(Ci.ffIManagerService);
  153.     this._dskSvc = mgrSvc.disk;
  154.     
  155.     //get the rdf service
  156.     this._rdfSvc = Cc["@mozilla.org/rdf/rdf-service;1"].
  157.                    getService(Ci.nsIRDFService);
  158.     
  159.     //get a temp file for the install manifest
  160.     this._temp = this._dskSvc.get("icon-pack.rdf", TYPE_TEMP);
  161.   },
  162.   
  163.   /**
  164.    * cleanup internal variables and close the zip reader.
  165.    *
  166.    * @param   The zip reader.
  167.    */
  168.   _destroy: function PackReader__destroy(aReader)
  169.   {
  170.     //close the reader
  171.     try {
  172.       aReader.close();
  173.     } catch(e) {}
  174.     
  175.     //remove the temp file
  176.     try {
  177.       removeFile(this._temp);
  178.     } catch(e) {}
  179.     
  180.     //clear variables
  181.     this._dskSvc = null;
  182.     this._rdfSvc = null;
  183.     this._temp = null;
  184.     this._bundle = null;
  185.   },  
  186.     
  187.   _setError: function PackReader__setError(aType, aData)
  188.   {
  189.     //get the error name and message
  190.     const PREFIX = "ff.packs.read.";
  191.     var name = this.bundle.GetStringFromName(PREFIX + "name");
  192.     var message = this.bundle.formatStringFromName(PREFIX + aType + ".message", 
  193.                                                    [aData], 1);
  194.     
  195.     //create an error item
  196.     this._item = Cc["@ensolis.com/forecastfox/error-item;1"].
  197.                  createInstance(Ci.ffIErrorItem);
  198.     
  199.     //set the error
  200.     this._item.init(SEVERITY_ERROR, name, message);  
  201.   },
  202.   
  203.   /**
  204.    * Get the entries in the zip file.
  205.    *
  206.    * @param   The zip reader.
  207.    * @return  An object containing the entries.
  208.    */
  209.   _getEntries: function PackReader__getEntries(aReader)
  210.   {
  211.     var rv = {};
  212.     
  213.     //loop through the entries
  214.     var entries = aReader.findEntries("*");
  215.     var entry = null;
  216.     
  217.     //toolkit 2.0 and prior or non toolkit
  218.     if ("hasMoreElements" in entries) {
  219.       while (entries.hasMoreElements()) {
  220.         entry = entries.getNext().QueryInterface(Ci.nsIZipEntry);
  221.         rv[entry.name] = entry.name;
  222.       }
  223.       
  224.     //toolkit 3.0
  225.     } else {
  226.       while (entries.hasMore()) {
  227.         entry = entries.getNext();
  228.         rv[entry] = entry;
  229.       }    
  230.     }
  231.     
  232.     //return the object
  233.     return rv;
  234.   },
  235.   
  236.   /**
  237.    * Check that all the required entries exist.
  238.    * 
  239.    * @param   An object containing the entries.
  240.    * @return  False if an entry is missing.
  241.    */
  242.   _checkEntries: function PackReader__checkEntries(aEntries)
  243.   {
  244.     //loop through required files
  245.     for (var i=0; i<PACK_FILES.length; i++) {
  246.       var file = PACK_FILES[i];
  247.       
  248.       //missing an entry
  249.       if (!aEntries.hasOwnProperty(file)) {
  250.         this._setError("entry", file);
  251.         return false;
  252.       }
  253.     }
  254.     
  255.     //all entries exist
  256.     return true;
  257.   },
  258.   
  259.   /**
  260.    * Extract the manifest file to a temp file and 
  261.    * load it into an RDF datasource.
  262.    *
  263.    * @param   The zip reader.
  264.    * @return  The manifest file.
  265.    */
  266.   _getManifest: function PackReader__getManifest(aReader)
  267.   {
  268.     //extract the file
  269.     aReader.extract("pack.rdf", this._temp);
  270.     
  271.     //load the datasource
  272.     var URL = this._dskSvc.getFileURL(this._temp);
  273.     return this._rdfSvc.GetDataSourceBlocking(URL);
  274.   },
  275.   
  276.   /**
  277.    * Check that all the required properties exist in the manifest.
  278.    * 
  279.    * @param   The manifest datasource.
  280.    * @return  False if a property is missing.
  281.    */
  282.   _checkManifest: function PackReader__checkManifest(aManifest)
  283.   {
  284.     //get the root resource    
  285.     var root = this._rdfSvc.GetResource(PACK_ROOT);
  286.     
  287.     //check for the required properties
  288.     for (var i=0; i<PACK_PROPERTIES.length; i++) {
  289.       var property = PACK_PROPERTIES[i];
  290.       
  291.       //get the property resource
  292.       var resource = this._rdfSvc.GetResource(PACK_PREFIX + property);
  293.       
  294.       //resource is missing
  295.       if (!aManifest.hasArcOut(root, resource)) {
  296.         this._setError("manifest", property);
  297.         this._rdfSvc.UnregisterDataSource(aManifest);
  298.         return false;
  299.       }
  300.     }
  301.     
  302.     //all properties exist
  303.     return true;  
  304.   },
  305.   
  306.   /**
  307.    * Create an icon pack from the pack manifest.
  308.    *
  309.    * @param   The manifest datasource.
  310.    * @return  An ffIPackItem object.
  311.    */
  312.   _createItem: function PackReader__createItem(aManifest)
  313.   {
  314.     //create an empty pack item
  315.     var pack = Cc["@ensolis.com/forecastfox/pack-item;1"].
  316.                createInstance(Ci.ffIPackItem);              
  317.  
  318.     //get the root resource    
  319.     var root = this._rdfSvc.GetResource(PACK_ROOT);
  320.     
  321.     //copy the required properties.        
  322.     for (var i=0; i<PACK_PROPERTIES.length; ++i) {
  323.       var property = PACK_PROPERTIES[i];
  324.       
  325.       //get the property resource
  326.       var resource = this._rdfSvc.GetResource(PACK_PREFIX + property);
  327.       
  328.       //set the pack property
  329.       var value = this._evalResource(aManifest, root, resource);
  330.       if (property == "id")
  331.         pack.setProperty("ID", value);
  332.       else 
  333.         pack.setProperty(property, value);
  334.     }
  335.     
  336.     //loop through exemptions
  337.     resource = this._rdfSvc.GetResource(PACK_PREFIX + "exemption");
  338.     var targets = aManifest.GetTargets(root, resource, true);
  339.     while (targets.hasMoreElements()) {
  340.       var target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
  341.       
  342.       //create an empty icon item
  343.       var icon = Cc["@ensolis.com/forecastfox/icon-item;1"].
  344.                  createInstance(Ci.ffIIconItem);              
  345.                
  346.       //get the exemption resources
  347.       var index = this._rdfSvc.GetResource(PACK_PREFIX + "index");
  348.       var size = this._rdfSvc.GetResource(PACK_PREFIX + "size");
  349.       var width = this._rdfSvc.GetResource(PACK_PREFIX + "width");
  350.       var height = this._rdfSvc.GetResource(PACK_PREFIX + "height");
  351.  
  352.       //set the values
  353.       icon.setProperty("index", this._evalResource(aManifest, target, index));
  354.       icon.setProperty("size", this._evalResource(aManifest, target, size));
  355.       icon.setProperty("width", this._evalResource(aManifest, target, width));
  356.       icon.setProperty("height", this._evalResource(aManifest, target, height));
  357.       icon.setProperty("isExemption", true);
  358.       
  359.       //add the icon to the pack
  360.       pack.setItem(icon);
  361.     }
  362.     
  363.     //unregister the datasource
  364.     this._rdfSvc.UnregisterDataSource(aManifest);
  365.         
  366.     //return the pack item
  367.     return pack;      
  368.   },
  369.   
  370.   /**
  371.    * Evaluate an RDF resource and return its value.
  372.    *
  373.    * @param   The manifest datasource.
  374.    * @param   The root resource.
  375.    * @param   The target resource.
  376.    * @return  The value of the target.
  377.    */
  378.   _evalResource: function PackReader__evalResource(aManifest, aRoot, aResource)
  379.   {
  380.     //get the target resource
  381.     var target = aManifest.GetTarget(aRoot, aResource, true);
  382.  
  383.     //return the targets value
  384.     if (target instanceof Ci.nsIRDFLiteral)
  385.       return target.Value;
  386.     if (target instanceof Ci.nsIRDFResource)
  387.       return target.Value;
  388.     if (target instanceof Ci.nsIRDFInt)
  389.       return target.Value;
  390.     
  391.     return "undefined";  
  392.   } 
  393. };
  394.  
  395.   
  396. /******************************************************************************
  397.  * Interface for describing the icon pack service.  This service should 
  398.  * be used for all profile data related changes.  
  399.  * 
  400.  * @status    FROZEN
  401.  * @version   1.0
  402.  ******************************************************************************/
  403. function PackService() 
  404. {
  405.   //setup additional interfaces
  406.   this._ifaces.push(Ci.nsIObserver);
  407.   this._ifaces.push(Ci.nsISupportsWeakReference);
  408.   
  409.   //setup a new error
  410.   this._error = Cc["@ensolis.com/forecastfox/error-item;1"].
  411.                 createInstance(Ci.ffIErrorItem);     
  412. }
  413. PackService.prototype = {
  414.   __proto__: new ServiceBase("PackService"),
  415.   _dskSvc: null,
  416.   _obSvc: null,
  417.   _items: null,
  418.   _isBatch: null,
  419.   _isLoading: null,
  420.   _flushPending: null,
  421.   
  422.   ///////////////////////////
  423.   // nsIObserver
  424.   observe: function PackService_observe(aSubject, aTopic, aData)
  425.   {
  426.     switch (aTopic) {
  427.       
  428.     //preference change
  429.     case "nsPref:changed":
  430.     
  431.       //ignore changes if we are loading
  432.       if (this.isLoading)
  433.         return;
  434.       
  435.       //current changed
  436.       this.current = this.getItem(getPref(aData));
  437.       break;
  438.     }
  439.   },
  440.                 
  441.   ////////////////////////////////
  442.   // ffIService        
  443.   
  444.   /**
  445.    * Initialize the component.  Called by the manager service.  Returns false
  446.    * if the datasource could not be loaded.  Chech the lastError property for
  447.    * more information.
  448.    */
  449.   start: function PackService_start()
  450.   {
  451.     //setup the variables
  452.     this._items = {};
  453.     this._isBatch = false;
  454.     this._isLoading = false;
  455.     this._flushPending = false;
  456.     
  457.     //get the disk service
  458.     var mgrSvc = Cc["@ensolis.com/forecastfox/manager-service;1"].
  459.                  getService(Ci.ffIManagerService);
  460.     this._dskSvc = mgrSvc.disk;
  461.     
  462.     //get the observer service
  463.     this._obSvc = Cc["@mozilla.org/observer-service;1"].
  464.                   getService(Ci.nsIObserverService);
  465.     
  466.     //start batch mode
  467.     this.startBatch();
  468.     
  469.     //remove old files
  470.     this._removeFiles(null);
  471.     
  472.     //load icon packs
  473.     var success = this._loadPacks();
  474.     
  475.     //end batch mode
  476.     this.endBatch();
  477.     
  478.     //setup a preference observer
  479.     var pb2 = this.branch.QueryInterface(Ci.nsIPrefBranchInternal);
  480.     pb2.addObserver("icons.current", this, true);
  481.     
  482.     //return our success code
  483.     return success;                        
  484.   },
  485.   
  486.   /**
  487.    * Destroy the component.  Called by the manager service.  This may be
  488.    * called prior to start so it needs to be safe.
  489.    */
  490.   stop: function PackService_stop()
  491.   {
  492.     //remove the pref observer
  493.     var pb2 = this.branch.QueryInterface(Ci.nsIPrefBranchInternal);
  494.     pb2.removeObserver("icons.current", this);
  495.  
  496.     //clear variables
  497.     this._dskSvc = null;
  498.     this._obSvc = null;
  499.     this._items = null;
  500.     this._isBatch = null;
  501.     this._isLoading = null;
  502.     this._flushPending = null;
  503.    },
  504.                 
  505.   ////////////////////////////////
  506.   // ffIPackService    
  507.  
  508.   /**
  509.    * Currently selected pack item.
  510.    */
  511.   get current() { return this._ensureItem(); }, 
  512.   
  513.   set current(aVal) {
  514.     //do nothing if null passed in
  515.     if (!aVal)
  516.       return;
  517.     
  518.     //do nothing if we don't have it
  519.     if (!this.hasItem(aVal.ID))
  520.       return;
  521.         
  522.     //load the items properties
  523.     this._items[aVal.ID] = aVal.clone();
  524.     this._loadItem(aVal);
  525.     
  526.     //notify observers
  527.     this._obSvc.notifyObservers(aVal, "forecastfox-icons", "current");
  528.   },
  529.   
  530.   /**
  531.    * Pack service is in batch mode.
  532.    */
  533.   get isBatch() { return this._isBatch; },
  534.  
  535.   /**
  536.    * Icon pack is being loaded into preferences.
  537.    */
  538.   get isLoading() { return this._isLoading; },
  539.   
  540.   /**
  541.    * Pack service has a specific item. 
  542.    * 
  543.    * @param   ID of the pack item.
  544.    * @return  True if item exists.
  545.    */
  546.   hasItem: function PackService_hasItem(aID)
  547.   {
  548.     return this._items.hasOwnProperty(aID);
  549.   },
  550.                            
  551.   /**
  552.    * Retrieve a pack item returns null if item doesn't exist.
  553.    * 
  554.    * @param   ID of the pack item.
  555.    * @return  A ffIPackItem.
  556.    */
  557.   getItem: function PackService_getItem(aID)
  558.   {
  559.     if (!this.hasItem(aID))
  560.       return null;
  561.     else
  562.       return this._items[aID];
  563.   },
  564.   
  565.   /**
  566.    * Retrieve an array of pack items.
  567.    * 
  568.    * @param   Count of items in the array.
  569.    * @return  An array of ffIPackItem components.
  570.    */
  571.   getItems: function PackService_getItems(aCount)
  572.   {    
  573.     //loop through items
  574.     var items = [];
  575.     for (var id in this._items)
  576.       items.push(this._items[id]);
  577.  
  578.     //sort the array
  579.     items.sort(sortByName);
  580.         
  581.     //return the array
  582.     aCount.value = items.length;
  583.     return items; 
  584.   },
  585.                   
  586.   /**
  587.    * Sets a pack item.  Returns false if pack could not be set.
  588.    * Use the lastError property for more information.
  589.    *
  590.    * @param   The icon pack file.
  591.    * @return  False if pack could not be set.
  592.    */
  593.   setItem: function PackService_setItem(aFile)
  594.   {
  595.     //setup error variables
  596.     const PREFIX = "ff.packs.set.";
  597.     var name = this.bundle.GetStringFromName(PREFIX + "name");
  598.     var message = "";  
  599.     
  600.     //error if the file does not exists
  601.     if (!aFile.exists()) {
  602.       message = this.bundle.GetStringFromName(PREFIX + "exist.message");
  603.       this._error.init(SEVERITY_ERROR, name, message);
  604.       return false;
  605.     }
  606.     
  607.     //error if the file is not readable
  608.     if (!aFile.isReadable()) {
  609.       message = this.bundle.formatStringFromName(PREFIX + "read.message",
  610.                                                  [aFile.path], 1);
  611.       this._error.init(SEVERITY_ERROR, name, message);
  612.       return false;
  613.     }
  614.     
  615.     //error if the file is not a jar
  616.     if (!aFile.leafName.match(".jar")) {
  617.       message = this.bundle.GetStringFromName(PREFIX + "jar.message");
  618.       this._error.init(SEVERITY_ERROR, name, message);
  619.       return false;
  620.     }
  621.     
  622.     //read the pack
  623.     var reader = new PackReader(aFile);
  624.     reader.run();
  625.     var item = reader.item;
  626.     
  627.     //check if the item is an error
  628.     if (item instanceof Ci.ffIErrorItem) {
  629.       this._error.init(item.severity, name, item.message);
  630.       return false;
  631.     }
  632.     
  633.     //copy the file to the icon directory
  634.     var file = this._dskSvc.get(aFile.leafName, TYPE_ICONS);
  635.     file.createUnique(file.NORMAL_FILE_TYPE, PERMS_FILE);   
  636.     try {
  637.       this._dskSvc.copy(aFile, file);
  638.     } catch(e) {
  639.       this._error.init(SEVERITY_ERROR, name, e.message);
  640.       return false;
  641.     }
  642.         
  643.     //remove the item if it is currently installed
  644.     var success = true;
  645.     if (this.hasItem(item.ID))
  646.       success = this.deleteItem(item.ID);
  647.       
  648.     //check if delete was successful
  649.     if (!success) {
  650.       try {
  651.         removeFile(file);
  652.       } catch(e) {}
  653.       var err = this.lastError;
  654.       this._error.init(err.severity, name, err.message);
  655.       return false;    
  656.     }
  657.     
  658.     //set the jar property
  659.     item.setProperty("jar", file.leafName);
  660.     
  661.     //save the item
  662.     this._items[item.ID] = item;
  663.     
  664.     //flush async
  665.     this._flushData();
  666.      
  667.     //notify observers
  668.     this._obSvc.notifyObservers(this, "forecastfox-icons", "setItem");
  669.     
  670.     //return successful
  671.     return true;
  672.   },
  673.     
  674.   /**
  675.    * Remove a pack item.  It also removes that pack file.
  676.    * Use the lastError property for more information.
  677.    * 
  678.    * @param   ID of the pack item.
  679.    * @return  False if pack could not be deleted.
  680.    */
  681.   deleteItem: function PackService_deleteItem(aID)
  682.   {
  683.     //do nothing if we don't have item
  684.     if (!this.hasItem(aID))
  685.       return false;
  686.     
  687.     //if the item is the current then load the next one
  688.     if (!this.isBatch && this.current.ID == aID)
  689.       this.current = this._nextItem();
  690.      
  691.     //remove the items file
  692.     this._removeFiles(this._items[aID].getProperty("jar"));
  693.       
  694.     //remove the item
  695.     delete this._items[aID];
  696.          
  697.     //flush async
  698.     this._flushData();
  699.      
  700.     //notify observers
  701.     this._obSvc.notifyObservers(this, "forecastfox-icons", "deleteItem");  
  702.     
  703.     //return successful
  704.     return true;
  705.   },
  706.                        
  707.   /**
  708.    * Turn on batch mode. Observers will get a notification of this.
  709.    * They will still get notified for every individual change, but they are
  710.    * free to ignore those notifications.
  711.    * Use this when a lot of changes are about to happen, and it would be
  712.    * useless to refresh the display (or the backend store) for every change.
  713.    * Caller must make sure to also call endBatch. Make sure all errors
  714.    * are caught!
  715.    */                     
  716.   startBatch: function PackService_startBatch()
  717.   {
  718.     //do nothing if already batch
  719.     if (this.isBatch)
  720.       return;
  721.          
  722.     //mark batch
  723.     this._isBatch = true; 
  724.          
  725.     //notify observers
  726.     this._obSvc.notifyObservers(this, "forecastfox-icons", "startBatch"); 
  727.   },
  728.    
  729.   /**
  730.    * Turn off batch mode.
  731.    */  
  732.   endBatch: function PackService_endBatch()
  733.   {
  734.     //do nothing if not batch
  735.     if (!this.isBatch)
  736.       return;
  737.       
  738.     //make sure we have the default profile
  739.     if (!this.hasItem("default"))
  740.       var item = this._getDefaultItem();
  741.           
  742.     //make sure we have a current profile
  743.     if (!this.hasItem(getPref("icons.current")))
  744.       this.current = item;            
  745.            
  746.     //mark not batch
  747.     this._isBatch = false; 
  748.     
  749.     // flush data if we have pending changes
  750.     if (this._flushPending) {
  751.       this._flushData();
  752.       this._flushPending = false;
  753.     }
  754.             
  755.     //notify observers
  756.     this._obSvc.notifyObservers(this, "forecastfox-icons", "endBatch");    
  757.   },
  758.                       
  759.   ////////////////////////////////
  760.   // Internal Functions  
  761.  
  762.   /**
  763.    * Select the next pack item.
  764.    */
  765.   _nextItem: function PackService__nextItem()
  766.   {
  767.     //setup variables
  768.     var ids = [];
  769.     var index = 0;
  770.     var current = this.current.ID;
  771.     var found = false;
  772.     
  773.     //loop through items
  774.     for (var id in this._items) {
  775.     
  776.       //mark that we found the current
  777.       if (current == id)
  778.         found = true;
  779.         
  780.       //increment the index
  781.       if (!found)
  782.         index++;  
  783.         
  784.       //save the id
  785.       ids.push(id);  
  786.     }
  787.     
  788.     //return the next item
  789.     index = (index + 1 < ids.length)  ? index + 1 : 0;
  790.     return this._items[ids[index]];
  791.   },
  792.   
  793.   /**
  794.    * Load an item into preferences.
  795.    *
  796.    * @param   The item to load.
  797.    */
  798.   _loadItem: function PackService__loadItem(aItem)
  799.   {
  800.     //mark we are loading
  801.     this._isLoading = true;
  802.     
  803.     //set the current pref
  804.     setPref("icons.current", aItem.ID);  
  805.       
  806.     //mark we are finished loading
  807.     this._isLoading = false;    
  808.   },
  809.   
  810.   /**
  811.    * Ensure there is an item available for the current property.
  812.    *
  813.    * @return  A profile item.
  814.    */
  815.   _ensureItem: function PackService__ensureItem()
  816.   {
  817.     //get the current item
  818.     var item = this.getItem(getPref("icons.current"));
  819.     if (!item) {
  820.  
  821.       //get all items
  822.       var items = this.getItems({});
  823.       
  824.       //set to the first one
  825.       if (items.length > 0)
  826.         item = items[0];
  827.         
  828.       //get the default
  829.       else
  830.         item = this._getDefaultItem();
  831.     }
  832.     
  833.     //return the item
  834.     return item;  
  835.   },
  836.   
  837.   /**
  838.    * Get the default icon pack item.
  839.    *
  840.    * @return  A ffIPackItem.
  841.    */
  842.   _getDefaultItem: function PackService__getDefaultItem()
  843.   {
  844.     //log that we are installing the default
  845.     this._dskSvc.log("Installing the default icon pack.", null, null);
  846.     
  847.     //get the default.jar file
  848.     var file = this._dskSvc.get("default.jar", TYPE_DEFAULTS);
  849.     
  850.     //install the item
  851.     var success = this.setItem(file);
  852.     
  853.     //install was unsuccessful
  854.     var item;
  855.     if (!success) {
  856.       this._dskSvc.log("Failed to install the default pack because: " + 
  857.                        this.lastError.toString(), null, null);
  858.       item = Cc["@ensolis.com/forecastfox/pack-item;1"].
  859.              createInstance(Ci.ffIPackItem);
  860.       this._items["default"] = item;
  861.       this._flushData();
  862.       
  863.     //install was successful
  864.     } else
  865.       item = this.getItem("default");
  866.     
  867.     //return the item
  868.     return item; 
  869.   },
  870.   
  871.   /**
  872.    * Try to remove old icon pack files that are marked for deletion.
  873.    *
  874.    * @param   A file to try removing
  875.    */
  876.   _removeFiles: function PackService__removeFiles(aName)
  877.   {
  878.     //get the names stored in prefs
  879.     var names = getPref("icons.uninstallfiles");
  880.     if (!aName && names == "")
  881.       return;
  882.     
  883.     //split the names into an array and add the passed in name
  884.     if (names != "")
  885.       names = names.split(";");
  886.     else
  887.       names = [];
  888.     if (aName)
  889.       names.push(aName);
  890.     
  891.     //loop through the file names
  892.     var dir = this._dskSvc.get("", TYPE_ICONS);
  893.     var remaining = [];
  894.     for (var i=0; i<names.length; i++) {
  895.       var file = dir.clone();
  896.       file.append(names[i]);
  897.       
  898.       //skip if file does not exist
  899.       if (!file.exists()) 
  900.         continue;
  901.       
  902.       //try to remove the file
  903.       try {
  904.         removeFile(file);
  905.         
  906.       //save the name since we could not remove it  
  907.       } catch (e) {
  908.         remaining.push(file.leafName);
  909.       }
  910.     }
  911.     
  912.     //save the remaining files back to prefs
  913.     setPref("icons.uninstallfiles", remaining.join(";"));
  914.   },
  915.   
  916.   /**  
  917.    * Create a pack item from a DOM node.
  918.    * 
  919.    * @param   The pack DOM node.
  920.    * @return  A pack item.
  921.    */  
  922.   _createItem: function PackService__createItem(aNode)
  923.   {
  924.     //create an empty pack item
  925.     var item = Cc["@ensolis.com/forecastfox/pack-item;1"].
  926.                createInstance(Ci.ffIPackItem);
  927.     
  928.     //set the name and ID
  929.     item.setProperty("name", aNode.getAttribute("name"));
  930.     item.setProperty("ID", aNode.getAttribute("id"));
  931.     
  932.     //loop through property nodes
  933.     var properties = aNode.getElementsByTagName("properties")[0];
  934.     properties = properties.getElementsByTagName("property");
  935.     for (var i=0; i<properties.length; i++) {
  936.       var property = properties[i];
  937.       
  938.       //get the property node attributes
  939.       var name = property.getAttribute("name");
  940.       var value = property.getAttribute("value");
  941.       var type = property.getAttribute("type");
  942.       
  943.       //set the item property based on type
  944.       switch (type) {
  945.       case "Int":
  946.         item.setProperty(name, Number(value));
  947.         break;
  948.       case "Bool":
  949.         item.setProperty(name, Boolean(value));
  950.         break;
  951.       case "Char":
  952.       default:
  953.         item.setProperty(name, value);
  954.         break;
  955.       }
  956.     }
  957.     
  958.     //loop through the exemption nodes
  959.     var exemptions = aNode.getElementsByTagName("exemptions")[0];
  960.     exemptions = exemptions.getElementsByTagName("exemption");
  961.     for (i=0; i<exemptions.length; i++) {
  962.     
  963.       //create an empty icon item
  964.       var icon = Cc["@ensolis.com/forecastfox/icon-item;1"].
  965.                  createInstance(Ci.ffIIconItem);
  966.                  
  967.       //loop through property nodes
  968.       properties = exemptions[i].getElementsByTagName("property");
  969.       for (var j=0; j<properties.length; j++) {
  970.         property = properties[j];
  971.       
  972.         //get the property node attributes
  973.         name = property.getAttribute("name");
  974.         value = property.getAttribute("value");
  975.         type = property.getAttribute("type");
  976.       
  977.         //set the icon property based on type
  978.         switch (type) {
  979.         case "Int":
  980.           icon.setProperty(name, Number(value));
  981.           break;
  982.         case "Bool":
  983.           icon.setProperty(name, Boolean(value));
  984.           break;
  985.         case "Char":
  986.         default:
  987.           icon.setProperty(name, value);
  988.           break;
  989.         }
  990.       }
  991.  
  992.       //add the icon to the pack
  993.       icon.setProperty("isExemption", true);
  994.       item.setItem(icon);
  995.     }
  996.     
  997.     //return the item
  998.     return item;
  999.   },
  1000.   
  1001.   
  1002.   /**
  1003.    * Create a DOM node from a pack item.
  1004.    * 
  1005.    * @param   The pack item.
  1006.    * @param   The owner document of the node.
  1007.    * @return  The DOM node.
  1008.    */  
  1009.   _createNode: function PackService__createNode(aItem, aDoc)
  1010.   {
  1011.     //create pack, properties, and exemptions nodes
  1012.     var pack = aDoc.createElementNS(ICONS_NS, "pack");
  1013.     var properties = aDoc.createElementNS(ICONS_NS, "properties");
  1014.     var exemptions = aDoc.createElementNS(ICONS_NS, "exemptions");
  1015.     
  1016.     //set the name and id
  1017.     pack.setAttribute("id", aItem.ID);
  1018.     pack.setAttribute("name", aItem.name);
  1019.     
  1020.     //loop through the items properties
  1021.     var props = aItem.properties;
  1022.     while (props.hasMore()) {
  1023.       var name = props.getNext();
  1024.       
  1025.       //skip ID and name properties
  1026.       if (name == "ID" || name == "name")
  1027.         continue;
  1028.        
  1029.       //set the node properties
  1030.       var node = aDoc.createElementNS(ICONS_NS, "property");
  1031.       var value = aItem.getProperty(name);
  1032.       node.setAttribute("name", name);
  1033.       node.setAttribute("value", String(value));
  1034.       if (typeof value == "number")
  1035.         node.setAttribute("type", "Int");
  1036.       else if (typeof value == "boolean")
  1037.         node.setAttribute("type", "Bool");
  1038.       else
  1039.         node.setAttribute("type", "Char");
  1040.       
  1041.       //append it to the properties node
  1042.       properties.appendChild(node);          
  1043.     }
  1044.     
  1045.     //loop through the packs icon items
  1046.     var items = aItem.getItems(true, {});
  1047.     for (var i=0; i<items.length; i++) {
  1048.       var item = items[i];
  1049.         
  1050.       //loop through the items properties    
  1051.       var exemption = aDoc.createElementNS(ICONS_NS, "exemption");
  1052.       props = item.properties;
  1053.       while (props.hasMore()) {
  1054.         name = props.getNext();
  1055.       
  1056.         //skip URL and isExemption
  1057.         if (name == "URL" || name == "isExemption")
  1058.           continue; 
  1059.        
  1060.         //set the node properties
  1061.         node = aDoc.createElementNS(ICONS_NS, "property");
  1062.         value = item.getProperty(name);
  1063.         node.setAttribute("name", name);
  1064.         node.setAttribute("value", String(value));
  1065.         if (typeof value == "number")
  1066.           node.setAttribute("type", "Int");
  1067.         else if (typeof value == "boolean")
  1068.           node.setAttribute("type", "Bool");
  1069.         else
  1070.           node.setAttribute("type", "Char");
  1071.       
  1072.         //append it to the exemption node
  1073.         exemption.appendChild(node);          
  1074.       }
  1075.       
  1076.       //append it to the exemptions node
  1077.       exemptions.appendChild(exemption);          
  1078.     }
  1079.     
  1080.     //append properties and exemptions
  1081.     pack.appendChild(properties);
  1082.     pack.appendChild(exemptions);
  1083.     
  1084.     //return the node
  1085.     return pack;
  1086.   },
  1087.  
  1088.   /**
  1089.    * Flush changes to disk.
  1090.    *
  1091.    * @param   Call file writer in blocking mode.
  1092.    */
  1093.   _flushData: function PackService__flushData()
  1094.   {
  1095.     //do nothing if in batch mode
  1096.     if (this.isBatch) {
  1097.       this._flushPending = true;
  1098.       return;
  1099.     }
  1100.     
  1101.     //do nothing if not writable
  1102.     var file = this._dskSvc.get("icons.xml", TYPE_PROFILE);
  1103.     if ((!file.exists() && !file.parent.isWritable()) ||
  1104.         (file.exists() && !file.isWritable())) {
  1105.       this._dskSvc.log("Icon pack file is not writable: " + file.path, 
  1106.                        null, null);
  1107.       return;
  1108.     }
  1109.          
  1110.     //create a new icons document
  1111.     var doc = this._dskSvc.create("icons", ICONS_DTD, ICONS_NS);
  1112.     
  1113.     //loop through items
  1114.     for (var id in this._items) {
  1115.       var item = this._items[id];
  1116.       
  1117.       //create the node
  1118.       var node = this._createNode(item, doc);
  1119.       
  1120.       //append it to the document
  1121.       doc.documentElement.appendChild(node);
  1122.     }
  1123.      
  1124.     //write to disk
  1125.     this._dskSvc.write(file, doc, false, false);
  1126.   },
  1127.      
  1128.   /**
  1129.    * Load the icon packs datasource.
  1130.    *
  1131.    * @return  False if we could not get the packs loaded.
  1132.    */
  1133.   _loadPacks: function PackService__loadPacks()
  1134.   {
  1135.     //setup error variables
  1136.     const PREFIX = "ff.packs.load.";
  1137.     var name = this.bundle.GetStringFromName(PREFIX + "name");
  1138.     var message = "";  
  1139.     
  1140.     //get icons.xml
  1141.     var dir = this._dskSvc.get("", TYPE_ICONS);
  1142.     var file = this._dskSvc.get("icons.xml", TYPE_PROFILE);
  1143.     if (file.exists()) {
  1144.     
  1145.       //file not readable-writable
  1146.       if (!file.isReadable() || !file.isWritable()) {
  1147.         message = this.bundle.formatStringFromName(PREFIX + ".perms.message",
  1148.                                                    [file.path], 1);
  1149.         this._error.init(SEVERITY_ERROR, name, message);
  1150.         return false;                                            
  1151.       }
  1152.       
  1153.       //read the file into a document
  1154.       var doc = this._dskSvc.read(file);
  1155.       
  1156.       //doc not valid
  1157.       if (!this._dskSvc.validate(doc, "icons")) {
  1158.         message = this.bundle.GetStringFromName(PREFIX + ".valid.message");
  1159.         this._error.init(SEVERITY_ERROR, name, message);
  1160.         return false;                                            
  1161.       }
  1162.       
  1163.       //loop through the pack nodes
  1164.       var isDirty = false;
  1165.       var nodes = doc.getElementsByTagName("pack");
  1166.       for (var i=0; i<nodes.length; i++) {
  1167.       
  1168.         //create an item
  1169.         var item = this._createItem(nodes[i]);
  1170.         
  1171.         //make sure the jar file exists
  1172.         if (!item.hasProperty("jar")) {
  1173.           isDirty = true;
  1174.           continue;
  1175.         } else {
  1176.           file = dir.clone();
  1177.           file.append(item.getProperty("jar"));
  1178.           if (!file.exists()) {
  1179.             isDirty = true;
  1180.             continue;
  1181.           }
  1182.         }
  1183.         
  1184.         //save the item
  1185.         this._items[item.ID] = item; 
  1186.       }
  1187.       
  1188.       // flush the data if it changed
  1189.       if (isDirty)
  1190.         this._flushData();
  1191.       
  1192.     //icons.xml does not exist
  1193.     } else {
  1194.       
  1195.       //log that datasource was missing
  1196.       this._dskSvc.log("Expected icons.xml, but was missing.", null, null);
  1197.       
  1198.     
  1199.       //file not readable-writable
  1200.       if (!file.parent.isReadable() || !file.parent.isWritable()) {
  1201.         message = this.bundle.formatStringFromName(PREFIX + ".perms.message",
  1202.                                                    [file.path], 1);
  1203.         this._error.init(SEVERITY_ERROR, name, message);
  1204.         return false;                                            
  1205.       }
  1206.       
  1207.       //loop through files in the directory
  1208.       var files = dir.directoryEntries;
  1209.       var todo = [];
  1210.       while (files.hasMoreElements()) {
  1211.       
  1212.         //save the file name to an array
  1213.         file = files.getNext().QueryInterface(Ci.nsIFile);
  1214.         if (file.leafName.match(".jar"))        
  1215.           todo.push(file.leafName);
  1216.         else
  1217.           this._removeFiles(file.leafName);
  1218.       }
  1219.       
  1220.       // loop backward through the array
  1221.       while(todo.length) {
  1222.       
  1223.         //install the file
  1224.         file = this._dskSvc.get(todo.pop(), TYPE_ICONS);     
  1225.         var success = this.setItem(file);
  1226.         if (!success)
  1227.          this._dskSvc.log("Failed to install icon pack because: " + 
  1228.                           this.lastError.toString(), null, null);
  1229.         
  1230.         //remove the old file
  1231.         this._removeFiles(file.leafName);
  1232.       }
  1233.     }
  1234.     
  1235.     //ensure we have the default icon pack
  1236.     if (!this.hasItem("default"))
  1237.       this.current = this._getDefaultItem();
  1238.        
  1239.     //load is successful
  1240.     return true;
  1241.   }
  1242. };